# -*- mode: python -*-
###
#
# (c) 2000-2007 Wijnand Modderman <wijnand at tehmaze dot com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

__revision__ = '$Hg:$'

# system imports
import math
import cmath
import re
import types

# DDB imports
from ddb.log import log

# DDB exports
EXPORT=['Calc']

_re_math = re.compile(r'(0x[a-f\d]+|'
                      r'0[0-7]+|'
                      r'b[01]+|'
                      r'\d+\.\d+|'
                      r'\.\d+|'
                      r'\d+\.|'
                      r'\d+|'
                      r'[MCDXLIV]+)') 

class Calc:              
    _math_const = { 'intmax': 2147483647, 'intmin': -2147483647, 'floatmax': 1.79769313486e+308, 'ans': 0 }

    def __init__(self):
        self._math_out = {
            'bin': self._2bin, 
            'hex': self._2hex, 
            'oct': self._2oct, 
            'rom': self._2rom
            }

    def _2bin(self, x):
        x=long(x)
        l=["000","001","010","011","100","101","110","111"]
        return 'b%s' % "".join([l[int(c)] for c in "%o"%x])
        
    def _2hex(self, x):
        return hex(int(x))
        
    def _2oct(self, x):
        #return "iets met oct ", x
        return ord(int(x))
        
    def _2rom(self, x):
        try:
            x = int(x)
        except:
            raise ValueError("%s is not a valid number" % x)
        if x > 4000:
            raise OverflowError
        ints = (1000, 900,  500, 400, 100,  90, 50,  40, 10,  9,   5,  4,   1)
        nums = ('M',  'CM', 'D', 'CD','C', 'XC','L','XL','X','IX','V','IV','I')
        result = ""
        for i in range(len(ints)):
            c = int(x / ints[i])
            result += nums[i] * c
            x -= ints[i] * c
        return result
        
    def _strfloat(self, x):
        if -1e-10 < x < 1e-10:
            return '0'
        elif -1e-10 < int(x) - x < 1e-10:
            return str(int(x))
        else:
            return str(x)

    def _strcomplex(self, x):
        _real = self._strfloat(x.real)
        _imag = self._strfloat(x.imag)
        if _imag == '0':
            return _real
        elif _imag == '1':
            _imag = '+i'
        elif _image == '-1':
            _imag = '-i'
        elif x.imag < 0:
            _imag = '%si' % _imag
        else:
            _imag = '+%si' % _imag
        if _real == '0' and _imag == '0':
            return '0'
        elif _real == '0':
            return _imag.lstrip('+')
        elif _imag == '0':
            return _real
        else:
            return '%s%s' % (_real, _imag)


    def calc(self, event):
        """<-o:<[hex]|[oct]|[bin]|[rom]> <sum>

        Calculate the output of <sum>. You can get the output in <hex>adecimal,
        <oct>agonal, <bin>ary or <rom>an numbers."""

        args = event.args.split()
        
        o = None
        if len(args):
            if args[0].startswith('-o:'):
                method = args[0].split(':',1)[1]
                if method in self._math_out.keys():
                    o = self._math_out[method]
                    args.pop(0)        
                else:
                    ok = self._math_out.keys()
                    ok.sort()
                    event.reply('invalid out type, chose either one of: %s' % ','.join(ok))
                    return
        args = ' '.join(args).lower()

        if not args or not len(args):
            event.reply(syntax())
            return

        for c in self._math_const.keys():
            args = args.replace(c, str(self._math_const[c]))
        s = args.replace('^', '**')

        #event.reply('will calc %s' % s)

        # make an eval 'jail'
        _math = {'__builtins__': types.ModuleType('__builtins__'), 'i': 1j}
        _math.update(math.__dict__)
        _math.update(cmath.__dict__)
        def _sqrt(x):
            if isinstance(x, complex) or x < 0:
                return cmath.sqrt(x)
            else:
                return math.sqrt(x)
        _math['sqrt'] = _sqrt
        _math['abs'] = abs
        _math['min'] = min
        _math['max'] = max

        # internal number converter
        def _calc(m):
            _s = m.group(1)
            if _s.startswith('0x'):
                # it's hexadecimal
                i = float(int(_s, 16))
            elif _s.startswith('0') and not '.' in _s:
                # it's octagonal
                try:
                    i = float(int(_s, 8))
                # oh it was not :)
                except ValueError:
                    i = float(int(_s))
            elif _s.startswith('b'):
                # it's binary
                _s = _s[1:] # shift off the b
                i = 0
                for digit in _s:
                    i = i*2 + "01".index(digit)
                i = float(i)
        #    elif 'M' in _s or 'D' in _s or 'C' in _s or 'L' in _s or 'X' in _s or 'V' in _s or 'I' in _s:
        #        raise ValueError, "roman input is not supported yet"                                                                            raise ValueError, 'input is not a valid roman numeral: %s' % input
            else:
                i = float(_s)
            x = complex(i)
            if x == abs(x):
                x = abs(x)
            return str(x)
        
        s = _re_math.sub(_calc, s)

        if 'lambda' in s:
            event.reply('lambda is not a valid function')
            return
        
        if s != s.encode('ascii','ignore'):
            event.reply('please use only ascii characters')
            return

        try:
            x = complex(eval(s, _math, _math))
            #x = float(eval(s, _math, _math))
            if o:
                x = o(x)
            event.reply('%s = %s' % (args, self._strcomplex(x)))
            self._math_const['ans'] = x
        except OverflowError:
            event.reply('answer > %s or so' % math.ldexp(0.9999999999999999, 1024))
        except TypeError:
            event.reply('invalid number')
        except NameError,e:
            event.reply('%s is not a valid function' % str(e).split()[1])
        except SyntaxError:
            event.reply('syntax error')
        except ZeroDivisionError:
            event.reply('division by zero')
        except Exception,e:
            event.commands._handleException(event, e)
    
# vim:ts=4:sw=4:et:
